module;
#include <infra/Cpp20.h>
#include <xx_three/ex.h>
module two.xxthree;

using namespace two; 

//<script src="js/geometries/LightningStrike.js"></script>
//<script src="js/objects/LightningStorm.js"></script>
//<script src="js/SimplexNoise.js"></script>
//<script src="js/postprocessing/OutlinePass.js"></script>

EX(xx_lightning)
{
#if 0
			var currentSceneIndex = 0;

			var currentTime = 0;

			var sceneCreators = [
				createConesScene,
				createPlasmaBallScene,
				createStormScene
			];

			var textureLoader;

			var clock = new THREE.Clock();
			
			var raycaster = new THREE.Raycaster();
			var mouse = new THREE.Vector2();

			init();
			animate();

			function init() {

				container = document.getElementById('container');

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio(window.devicePixelRatio);
				renderer.setSize(window.innerWidth, window.innerHeight);

				renderer.gammaInput = true;
				renderer.gammaOutput = true;

				container.appendChild(renderer.domElement);

				composer = new THREE.EffectComposer(renderer);

				stats = new Stats();
				container.appendChild(stats.dom);

				window.addEventListener('resize', onWindowResize, false);

				textureLoader = new THREE.TextureLoader();

				createScene();

			}

			function createScene() {

				scene = sceneCreators[currentSceneIndex]();

				createGUI();

			}

			function onWindowResize() {

				scene.userData.camera.aspect = window.innerWidth / window.innerHeight;
				scene.userData.camera.updateProjectionMatrix();

				renderer.setSize(window.innerWidth, window.innerHeight);
				
				composer.setSize(window.innerWidth, window.innerHeight);

			}

			//

			function createGUI() {

				if (gui) {
					gui.destroy();
				}

				gui = new dat.GUI({ width: 350 });

				var sceneFolder = gui.addFolder("Scene");

				scene.userData.sceneIndex = currentSceneIndex;

				sceneFolder.add(scene.userData, 'sceneIndex', { "Electric Cones": 0, "Plasma Ball": 1, "Storm": 2 }).name('Scene').onChange(function (value) {

					currentSceneIndex = value;

					createScene();

				});
				
				scene.userData.timeRate = 1;
				sceneFolder.add(scene.userData, 'timeRate', scene.userData.canGoBackwardsInTime ? -1 : 0, 1).name('Time rate');

				sceneFolder.open();

				var graphicsFolder = gui.addFolder("Graphics");

				graphicsFolder.add(scene.userData, "outlineEnabled").name("Glow enabled");

				scene.userData.lightningColorRGB = [
					scene.userData.lightningColor.r * 255,
					scene.userData.lightningColor.g * 255,
					scene.userData.lightningColor.b * 255
				];
				graphicsFolder.addColor(scene.userData, 'lightningColorRGB').name('Color').onChange(function (value) {
					scene.userData.lightningMaterial.color.setRGB(value[0], value[1], value[2]).multiplyScalar(1 / 255);
				});
				scene.userData.outlineColorRGB = [
					scene.userData.outlineColor.r * 255,
					scene.userData.outlineColor.g * 255,
					scene.userData.outlineColor.b * 255
				];
				graphicsFolder.addColor(scene.userData, 'outlineColorRGB').name('Glow color').onChange(function (value) {
					scene.userData.outlineColor.setRGB(value[0], value[1], value[2]).multiplyScalar(1 / 255);
				});

				graphicsFolder.open();

				var rayFolder = gui.addFolder("Ray parameters");

				rayFolder.add(scene.userData.rayParams, 'straightness', 0, 1).name('Straightness');
				rayFolder.add(scene.userData.rayParams, 'roughness', 0, 1).name('Roughness');
				rayFolder.add(scene.userData.rayParams, 'radius0', 0.1, 10).name('Initial radius');
				rayFolder.add(scene.userData.rayParams, 'radius1', 0.1, 10).name('Final radius');
				rayFolder.add(scene.userData.rayParams, 'radius0Factor', 0, 1).name('Subray initial radius');
				rayFolder.add(scene.userData.rayParams, 'radius1Factor', 0, 1).name('Subray final radius');
				rayFolder.add(scene.userData.rayParams, 'timeScale', 0, 5).name('Ray time scale');
				rayFolder.add(scene.userData.rayParams, 'subrayPeriod', 0.1, 10).name('Subray period (s)');
				rayFolder.add(scene.userData.rayParams, 'subrayDutyCycle', 0, 1).name('Subray duty cycle');

				if (scene.userData.recreateRay) {
				
					// Parameters which need to recreate the ray after modification

					var raySlowFolder = gui.addFolder("Ray parameters (slow)");

					raySlowFolder.add(scene.userData.rayParams, 'ramification', 0, 15).step(1).name('Ramification').onFinishChange(function () {

						scene.userData.recreateRay();

					});

					raySlowFolder.add(scene.userData.rayParams, 'maxSubrayRecursion', 0, 5).step(1).name('Recursion').onFinishChange(function () {

						scene.userData.recreateRay();

					});

					raySlowFolder.add(scene.userData.rayParams, 'recursionProbability', 0, 1).name('Rec. probability').onFinishChange(function () {

						scene.userData.recreateRay();

					});

					raySlowFolder.open();

				}

				rayFolder.open();

			}

			//

			function animate() {

				requestAnimationFrame(animate);

				render();
				stats.update();

			}

			function render() {

				currentTime += scene.userData.timeRate * clock.getDelta();

				if (currentTime < 0) {

					currentTime = 0;

				}

				scene.userData.render(currentTime);

			}

			function createOutline(scene, objectsArray, visibleColor) {

				var outlinePass = new THREE.OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, scene.userData.camera, objectsArray);
				outlinePass.edgeStrength = 2.5;
				outlinePass.edgeGlow = 0.7;
				outlinePass.edgeThickness = 2.8;
				outlinePass.visibleEdgeColor = visibleColor;
				outlinePass.hiddenEdgeColor.set(0);
				outlinePass.renderToScreen = true;
				composer.addPass(outlinePass);

				scene.userData.outlineEnabled = true;
				
				return outlinePass;

			}

			//

			function createConesScene() {

				var scene = new THREE.Scene();
				scene.background = new THREE.Color(0x050505);

				scene.userData.canGoBackwardsInTime = true;

				scene.userData.camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 200, 100000);
				
				// Lights

				scene.userData.lightningColor = new THREE.Color(0xB0FFFF);
				scene.userData.outlineColor = new THREE.Color(0x00FFFF);

				var posLight = new THREE.PointLight(0x00ffff, 1, 5000, 2);
				scene.add(posLight);

				// Ground

				var ground = new THREE.Mesh(new THREE.PlaneBufferGeometry(200000, 200000), new THREE.MeshPhongMaterial({ color: 0xC0C0C0, shininess: 0 }));
				ground.rotation.x = - c_pi * 0.5;
				scene.add(ground);

				// Cones

				var conesDistance = 1000;
				var coneHeight = 200;
				var coneHeightHalf = coneHeight * 0.5;

				posLight.position.set(0, (conesDistance + coneHeight) * 0.5, 0);
				posLight.color = scene.userData.outlineColor;

				scene.userData.camera.position.set(5 * coneHeight, 4 * coneHeight, 18 * coneHeight);

				var coneMesh1 = new THREE.Mesh(new THREE.ConeBufferGeometry(coneHeight, coneHeight, 30, 1, false), new THREE.MeshPhongMaterial({ color: 0xFFFF00, emissive: 0x1F1F00 }));
				coneMesh1.rotation.x = c_pi;
				coneMesh1.position.y = conesDistance + coneHeight;
				scene.add(coneMesh1);

				var coneMesh2 = new THREE.Mesh(coneMesh1.geometry.clone(), new THREE.MeshPhongMaterial({ color: 0xFF2020, emissive: 0x1F0202 }));
				coneMesh2.position.y = coneHeightHalf;
				scene.add(coneMesh2);

				// Lightning strike

				scene.userData.lightningMaterial = new THREE.MeshBasicMaterial({ color: scene.userData.lightningColor });

				scene.userData.rayParams = {

					sourceOffset: new THREE.Vector3(),
					destOffset: new THREE.Vector3(),
					radius0: 4,
					radius1: 4,
					minRadius: 2.5,
					maxIterations: 7,
					isEternal: true,

					timeScale: 0.7,

					propagationTimeFactor: 0.05,
					vanishingTimeFactor: 0.95,
					subrayPeriod: 3.5,
					subrayDutyCycle: 0.6,
					maxSubrayRecursion: 3,
					ramification: 7,
					recursionProbability: 0.6,

					roughness: 0.85,
					straightness: 0.6

				};

				var lightningStrike;
				var lightningStrikeMesh;
				var outlineMeshArray = [];

				scene.userData.recreateRay = function () {

					if (lightningStrikeMesh) {
						scene.remove(lightningStrikeMesh);
					}

					lightningStrike = new THREE.LightningStrike(scene.userData.rayParams);
					lightningStrikeMesh = new THREE.Mesh(lightningStrike, scene.userData.lightningMaterial);

					outlineMeshArray.length = 0;
					outlineMeshArray.push(lightningStrikeMesh);

					scene.add(lightningStrikeMesh);

				}

				scene.userData.recreateRay();

				// Compose rendering

				composer.passes = [];
				composer.addPass(new THREE.RenderPass(scene, scene.userData.camera));
				createOutline(scene, outlineMeshArray, scene.userData.outlineColor);

				// Controls

				var controls = new THREE.OrbitControls(scene.userData.camera, renderer.domElement);
				controls.target.y = (conesDistance + coneHeight) * 0.5
				controls.enableDamping = true;
				controls.dampingFactor = 0.25;

				scene.userData.render = function (time) {

					// Move cones and Update ray position
					coneMesh1.position.set(sin(0.5 * time) * conesDistance * 0.6,  conesDistance + coneHeight, cos(0.5 * time) * conesDistance * 0.6);
					coneMesh2.position.set(sin(0.9 * time) * conesDistance, coneHeightHalf, 0);
					lightningStrike.rayParameters.sourceOffset.copy(coneMesh1.position);
					lightningStrike.rayParameters.sourceOffset.y -= coneHeightHalf;
					lightningStrike.rayParameters.destOffset.copy(coneMesh2.position);
					lightningStrike.rayParameters.destOffset.y += coneHeightHalf;

					lightningStrike.update(time);

					controls.update();

					// Update point light position to the middle of the ray
					posLight.position.lerpVectors(lightningStrike.rayParameters.sourceOffset, lightningStrike.rayParameters.destOffset, 0.5);

					if (scene.userData.outlineEnabled) {

						composer.render();

					}
					else {

						renderer.render(scene, scene.userData.camera);

					}

				};

				return scene;

			}

			//

			function createPlasmaBallScene() {

				var scene = new THREE.Scene();

				scene.userData.canGoBackwardsInTime = true;

				scene.userData.camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 100, 50000);

				var ballScene = new THREE.Scene();
				ballScene.background = new THREE.Color(0x454545);

				// Lights

				var ambientLight = new THREE.AmbientLight(0x444444);
				ballScene.add(ambientLight);
				scene.add(ambientLight);

				var light1 = new THREE.DirectionalLight(0xffffff, 0.5);
				light1.position.set(1, 1, 1);
				ballScene.add(light1);
				scene.add(light1);

				var light2 = new THREE.DirectionalLight(0xffffff, 1.5);
				light2.position.set(-0.5, 1, 0.2);
				ballScene.add(light2);
				scene.add(light2);

				// Plasma ball

				scene.userData.lightningColor = new THREE.Color(0xFFB0FF);
				scene.userData.outlineColor = new THREE.Color(0xFF00FF);

				scene.userData.lightningMaterial =  new THREE.MeshBasicMaterial({ color: scene.userData.lightningColor, side: THREE.DoubleSide });

				var r = "textures/cube/Bridge2/";
				var urls = [r + "posx.jpg", r + "negx.jpg",
							 r + "posy.jpg", r + "negy.jpg",
							 r + "posz.jpg", r + "negz.jpg"];

				var textureCube = new THREE.CubeTextureLoader().load(urls);
				textureCube.format = THREE.RGBFormat;
				textureCube.mapping = THREE.CubeReflectionMapping;

				var sphereMaterial = new THREE.MeshPhysicalMaterial({
					transparent: true,
					depthWrite: false,
					opacity: 0.15,
					color: 0,
					metalness: 1,
					roughness: 0,
					reflectivity: 0,
					envMap: textureCube
				});

				var sphereHeight = 300;
				var sphereRadius = 200;

				scene.userData.camera.position.set(5 * sphereRadius, 2 * sphereHeight, 6 * sphereRadius);

				var sphereMesh = new THREE.Mesh(new THREE.SphereBufferGeometry(sphereRadius, 80, 40), sphereMaterial);
				sphereMesh.position.set(0, sphereHeight, 0);
				ballScene.add(sphereMesh);

				var sphere = new THREE.Sphere(sphereMesh.position, sphereRadius);

				var spherePlasma = new THREE.Mesh(new THREE.SphereBufferGeometry(sphereRadius * 0.05, 24, 12), scene.userData.lightningMaterial);
				spherePlasma.position.copy(sphereMesh.position);
				spherePlasma.scale.y = 0.6;
				scene.add(spherePlasma);

				var post = new THREE.Mesh(
					new THREE.CylinderBufferGeometry(sphereRadius * 0.06, sphereRadius * 0.06, sphereHeight, 6, 1, true),
					new THREE.MeshLambertMaterial({ color: 0x020202 })
				);
				post.position.y = sphereHeight * 0.5 - sphereRadius * 0.05 * 1.2;
				scene.add(post);

				var box = new THREE.Mesh(new THREE.BoxBufferGeometry(sphereHeight * 0.5, sphereHeight * 0.1, sphereHeight * 0.5), post.material);
				box.position.y = sphereHeight * 0.05 * 0.5;
				scene.add(box);

				var rayDirection = new THREE.Vector3();
				var rayLength = 0;
				var vec1 = new THREE.Vector3();
				var vec2 = new THREE.Vector3();

				scene.userData.rayParams = {

					sourceOffset: sphereMesh.position,
					destOffset: new THREE.Vector3(sphereRadius, 0, 0).add(sphereMesh.position),
					radius0: 4,
					radius1: 4,
					radius0Factor: 0.82,
					minRadius: 2.5,
					maxIterations: 6,
					isEternal: true,

					timeScale: 0.6,
					propagationTimeFactor: 0.15,
					vanishingTimeFactor: 0.87,
					subrayPeriod: 0.8,
					ramification: 5,
					recursionProbability: 0.8,

					roughness: 0.85,
					straightness: 0.7,

					onSubrayCreation: function (segment, parentSubray, childSubray, lightningStrike) {

						lightningStrike.subrayConePosition(segment, parentSubray, childSubray, 0.6, 0.9, 0.7);
					
						// Sphere projection

						vec1.subVectors(childSubray.pos1, lightningStrike.rayParameters.sourceOffset);
						vec2.set(0, 0, 0);
						if (lightningStrike.randomGenerator.random() < 0.7) {
							vec2.copy(rayDirection).multiplyScalar(rayLength * 1.0865);
						}
						vec1.add(vec2).setLength(rayLength);
						childSubray.pos1.addVectors(vec1, lightningStrike.rayParameters.sourceOffset);

					}

				};

				var lightningStrike;
				var lightningStrikeMesh;
				var outlineMeshArray = [];
				
				scene.userData.recreateRay = function () {

					if (lightningStrikeMesh) {
						scene.remove(lightningStrikeMesh);
					}

					lightningStrike = new THREE.LightningStrike(scene.userData.rayParams);
					lightningStrikeMesh = new THREE.Mesh(lightningStrike, scene.userData.lightningMaterial);

					outlineMeshArray.length = 0;
					outlineMeshArray.push(lightningStrikeMesh);
					outlineMeshArray.push(spherePlasma);

					scene.add(lightningStrikeMesh);

				}

				scene.userData.recreateRay();

				// Compose rendering

				composer.passes = [];

				composer.addPass(new THREE.RenderPass(ballScene, scene.userData.camera));

				var rayPass = new THREE.RenderPass(scene, scene.userData.camera);
				rayPass.clear = false;
				composer.addPass(rayPass);

				var outlinePass = createOutline(scene, outlineMeshArray, scene.userData.outlineColor);

				scene.userData.render = function (time) {

					rayDirection.subVectors(lightningStrike.rayParameters.destOffset, lightningStrike.rayParameters.sourceOffset);
					rayLength = rayDirection.length();
					rayDirection.normalize();

					lightningStrike.update(time);

					controls.update();

					if (scene.userData.outlineEnabled) {

						outlinePass.enabled = true;
						rayPass.renderToScreen = false;

					}
					else {

						outlinePass.enabled = false;
						rayPass.renderToScreen = true;

					}

					composer.render();

				};

				// Controls

				var controls = new THREE.OrbitControls(scene.userData.camera, renderer.domElement);
				controls.target.copy(sphereMesh.position);
				controls.enableDamping = true;
				controls.dampingFactor = 0.25;

				// Sphere mouse raycasting

				window.addEventListener('mousemove', onTouchMove);
				window.addEventListener('touchmove', onTouchMove);

				function onTouchMove(event) {

					var x, y;

					if (event.changedTouches) {

						x = event.changedTouches[0].pageX;
						y = event.changedTouches[0].pageY;

					} else {

						x = event.clientX;
						y = event.clientY;

					}

					mouse.x = (x / window.innerWidth) * 2 - 1;
					mouse.y = - (y / window.innerHeight) * 2 + 1;

					checkIntersection();

				}

				var intersection = new THREE.Vector3();

				function checkIntersection() {

					raycaster.setFromCamera(mouse, scene.userData.camera);

					var result = raycaster.ray.intersectSphere(sphere, intersection);

					if (result !== null) {

						lightningStrike.rayParameters.destOffset.copy(intersection);

					}

				}

				return scene;

			}
			
			//
			
			function createStormScene() {

				var scene = new THREE.Scene();
				scene.background = new THREE.Color(0x050505);

				scene.userData.canGoBackwardsInTime = false;

				scene.userData.camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 20, 10000);

				// Lights

				scene.add(new THREE.AmbientLight(0x444444));

				var light1 = new THREE.DirectionalLight(0xffffff, 0.5);
				light1.position.set(1, 1, 1);
				scene.add(light1);

				var posLight = new THREE.PointLight(0x00ffff);
				posLight.position.set(0, 100, 0);
				scene.add(posLight);

				// Ground

				var GROUND_SIZE = 1000;

				scene.userData.camera.position.set(0, 0.2, 1.6).multiplyScalar(GROUND_SIZE * 0.5);

				var ground = new THREE.Mesh(new THREE.PlaneBufferGeometry(GROUND_SIZE, GROUND_SIZE), new THREE.MeshLambertMaterial({ color: 0x072302 }));
				ground.rotation.x = - c_pi * 0.5;
				scene.add(ground);

				// Storm

				scene.userData.lightningColor = new THREE.Color(0xB0FFFF);
				scene.userData.outlineColor = new THREE.Color(0x00FFFF);

				scene.userData.lightningMaterial =  new THREE.MeshBasicMaterial({ color: scene.userData.lightningColor });

				var rayDirection = new THREE.Vector3(0, -1, 0);
				var vec1 = new THREE.Vector3();
				var vec2 = new THREE.Vector3();

				scene.userData.rayParams = {

					radius0: 1,
					radius1: 0.5,
					minRadius: 0.3,
					maxIterations: 7,

					timeScale: 0.15,
					propagationTimeFactor: 0.2,
					vanishingTimeFactor: 0.9,
					subrayPeriod: 4,
					subrayDutyCycle: 0.6,

					maxSubrayRecursion: 3,
					ramification: 3,
					recursionProbability: 0.4,

					roughness: 0.85,
					straightness: 0.65,

					onSubrayCreation: function (segment, parentSubray, childSubray, lightningStrike) {

						lightningStrike.subrayConePosition(segment, parentSubray, childSubray, 0.6, 0.6, 0.5);

						// Plane projection
						
						rayLength = lightningStrike.rayParameters.sourceOffset.y;
						vec1.subVectors(childSubray.pos1, lightningStrike.rayParameters.sourceOffset);
						var proj = rayDirection.dot(vec1);
						vec2.copy(rayDirection).multiplyScalar(proj);
						vec1.sub(vec2);
						var scale = proj / rayLength > 0.5 ? rayLength / proj : 1;
						vec2.multiplyScalar(scale);
						vec1.add(vec2);
						childSubray.pos1.addVectors(vec1, lightningStrike.rayParameters.sourceOffset);

					}

				};

				// Black star mark
				var starVertices = [];
				var prevPoint = new THREE.Vector3(0, 0, 1);
				var currPoint = new THREE.Vector3();
				for(var i = 1; i <= 16; i++) {

					currPoint.set(sin(2 * c_pi * i / 16), 0, cos(2 * c_pi * i / 16));

					if (i % 2 === 1) {

						currPoint.multiplyScalar(0.3);

					}

					starVertices.push(0, 0, 0);
					starVertices.push(prevPoint.x, prevPoint.y, prevPoint.z);
					starVertices.push(currPoint.x, currPoint.y, currPoint.z);

					prevPoint.copy(currPoint);

				}

				var starGeometry = new THREE.BufferGeometry();
				starGeometry.addAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
				var starMesh = new THREE.Mesh(starGeometry, new THREE.MeshBasicMaterial({ color: 0x020900 }));
				starMesh.scale.multiplyScalar(6);
				
				//

				var storm = new THREE.LightningStorm({

					size: GROUND_SIZE,
					minHeight: 90,
					maxHeight: 200,
					maxSlope: 0.6,
					maxLightnings: 8,

					lightningParameters: scene.userData.rayParams,

					lightningMaterial: scene.userData.lightningMaterial,

					onLightningDown: function (lightning) {

						// Add black star mark at ray strike
						var star1 = starMesh.clone();
						star1.position.copy(lightning.rayParameters.destOffset);
						star1.position.y = 0.05;
						star1.rotation.y = 2 * c_pi * random();
						scene.add(star1);

					}

				});

				scene.add(storm);

				// Compose rendering

				composer.passes = [];
				composer.addPass(new THREE.RenderPass(scene, scene.userData.camera));
				createOutline(scene, storm.lightningsMeshes, scene.userData.outlineColor);

				// Controls

				var controls = new THREE.OrbitControls(scene.userData.camera, renderer.domElement);
				controls.target.y = GROUND_SIZE * 0.05;
				controls.enableDamping = true;
				controls.dampingFactor = 0.25;

				scene.userData.render = function (time) {

					storm.update(time);

					controls.update();

					if (scene.userData.outlineEnabled) {

						composer.render();

					}
					else {

						renderer.render(scene, scene.userData.camera);

					}

				};

				return scene;
			}

#endif
}
